package block

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"encoding/gob"
	"fmt"
	"github.com/btcsuite/btcutil/base58"
	"log"
	"math/big"
	"strings"
)

//v5改版
//定义交易结构
//定义input
//定义output
//设置交易id

type TXInput struct {
	// 引用 utox所在交易id
	// 所消费utox在output中的索引
	TXID  []byte
	Index int64
	//Address string
	Signature []byte //交易签名
	Pubkey    []byte //公钥本身
}

type TXOutput struct {
	Value float64 // 转账金额
	//Address string  // 锁定脚本
	PubKeyHash []byte //公钥的hash
}

type Transaction struct {
	TXid      []byte     //交易id
	TXInputs  []TXInput  //所有的input
	TXOutputs []TXOutput //所有的TXOutput
}

func (tx *Transaction)String() string {
	var lines []string
	lines=append(lines,fmt.Sprintf("---Transaction %x",tx.TXid))

	for i,input:=range tx.TXInputs{
		lines=append(lines,fmt.Sprintf("	Input %d",i))
		lines=append(lines,fmt.Sprintf("		TXID %x",input.TXID))
		lines=append(lines,fmt.Sprintf("		Index %d",input.Index))
		lines=append(lines,fmt.Sprintf("		Signature %x",input.Signature))
		lines=append(lines,fmt.Sprintf("		Pubkey %x",input.Pubkey))
	}

	for i,output:=range tx.TXOutputs{
		lines=append(lines,fmt.Sprintf("	Input %d",i))
		lines=append(lines,fmt.Sprintf("		Value %f",output.Value))
		lines=append(lines,fmt.Sprintf("		PubKeyHash %x",output.PubKeyHash))
	}
	return strings.Join(lines,"\n")
}
// 给定转账地址，得到这个地址的公钥哈希，完成对output的锁定
func (output *TXOutput) Lock(address string) {
	// 把转账地址转为25字节数据
	publicInfo := base58.Decode(address)
	// 取1到20个字节数据
	pubKeyHash := publicInfo[1 : len(publicInfo)-4]
	output.PubKeyHash = pubKeyHash
}

func NewTXOutput(value float64, address string) TXOutput {
	output := TXOutput{
		Value:      value, //多少钱
		PubKeyHash: nil,   //公钥hash值
	}
	output.Lock(address)
	return output
}

// 设置TXid txid为交易哈希值
func (tx *Transaction) SetTXid() {
	var buffer bytes.Buffer
	encoder := gob.NewEncoder(&buffer)
	err := encoder.Encode(tx)
	if err != nil {
		log.Panicln(err)
	}
	// 做哈希处理
	hash := sha256.Sum256(buffer.Bytes())
	tx.TXid = hash[:]
}

//实现挖矿交易 v5
//特点：只有输出，没有有效输入。
// miner把挖矿的人公钥哈希传递进来，因为挖矿有奖励
//12.5为挖矿奖励 //data 矿区消息

func NewCoinbaseTx(miner string, data string) *Transaction {
	//TODO
	inputs := []TXInput{{nil, -1, nil, []byte(data)}}
	//output := []TXOutput{{reward, miner}}
	output := NewTXOutput(reward, miner)
	outputs := []TXOutput{output}
	tx := Transaction{
		nil,

		inputs,
		outputs,
	}
	tx.SetTXid()
	return &tx
}

/**
判断是否是挖矿交易
*/
func (tx *Transaction) ISCoinbase() bool {
	//特点：1.只有一个input
	//如L，inputs := []TXInput{{nil, -1, data}}
	// 2.引用id是nil，3.引用index是-1

	inputs := tx.TXInputs
	if len(inputs) == 1 && inputs[0].TXID == nil && inputs[0].Index == -1 {
		return true
	}
	return false
}

/**
//创建普通交易
//内部逻辑
1. 遍历账本，找到属于付款人的合适的金额，把这个outputs找到。
2. 如果找到前不足以转账，创建交易失败
3. 将outputs转成inputs
4. 创建输出，创建一个属于收款人的output
5. 如果有找零，创建属于付款人的output
6. 设置交易id
7. 返回交易结构
*/

/**
参数：付款人base58公钥，收款人，转账金额，bc
*/
func NewTransaction(from, to string, amout float64, bc *BlockChain) *Transaction {

	//1.打开钱包
	ws := NewWallets()

	//获取密钥对
	wallet := ws.WalletsMap[from]
	if wallet == nil {
		fmt.Println("公钥哈希：", from, ",不存在，交易失败！")
		return nil
	}
	//2.获取公钥，私钥
	privateKey := wallet.PrivateKey //暂时不用
	publicKey := wallet.PublicKey

	pubKeyHash := HashPubHash(wallet.PublicKey)

	utxos := make(map[string][]int64) //标识能用的utxo
	var resValue float64              //这些utxo存储的余额
	// 参数：付款人，转账金额 1. 遍历账本，找到属于付款人的合适的金额，把这个outputs找到。
	utxos, resValue = bc.FindNeedUtoxs(pubKeyHash, amout)
	//2. 如果找到前不足以转账，创建交易失败
	if resValue < amout {
		fmt.Println("区块链中，你的所有余额不足，交易失败！")
		return nil
	}
	var inputs []TXInput
	var outputs []TXOutput
	//3. 将outputs转成inputs
	for txid, values := range utxos {
		for _, i := range values {
			input := TXInput{
				TXID:   []byte(txid),
				Index:  i,
				Pubkey: publicKey,
			}
			inputs = append(inputs, input)
		}
	}

	/**
	4. 创建输出，创建一个属于收款人的output
	5. 如果有找零，创建属于付款人的output
	6. 设置交易id
	7. 返回交易结构
	*/
	//4. 创建输出，创建一个属于收款人的output
	output := NewTXOutput(amout, to)
	outputs = append(outputs, output)
	//5. 如果有找零，创建属于付款人的output
	if resValue > amout {
		//就把剩余的余额，创建一个自己的output
		//output_this := TXOutput{
		//	Value:   resValue - amout,
		//	PubKeyHash: from,
		//}
		output_this := NewTXOutput(resValue-amout, from)
		outputs = append(outputs, output_this)
	}
	//创建交易
	tx := Transaction{
		TXid:      nil,
		TXInputs:  inputs,
		TXOutputs: outputs,
	}
	tx.SetTXid()

	bc.SignTransaction(&tx, privateKey)

	return &tx
}

/*
privKey：私钥
prevTXs：交易的input所引用的所有的交易
*/
func (tx *Transaction) Sign(privKey *ecdsa.PrivateKey, prevTXs map[string]Transaction) {
	fmt.Println("对交易进行签名！")
	txCopy := tx.TrimmedCopy()
	//遍历txcopy。inouts 把这个input所引用的output的公钥哈希拿出来，赋值给pubkey
	for i, input := range txCopy.TXInputs {
		//找到引用的交易
		preTX := prevTXs[string(input.TXID)]
		output := preTX.TXOutputs[input.Index]
		txCopy.TXInputs[i].Pubkey = output.PubKeyHash
		//签名要对数据的hash进行签名
		//我们的数据都在交易中，我们要求交易的哈希
		//tr中的settxid函数就是对交易的哈希
		//所以我们可以使用交易id作为我们的签名的内容
		txCopy.SetTXid()
		signData := txCopy.TXid
		//清理
		txCopy.TXInputs[i].Pubkey = nil
		//生成要签名的数据 哈希
		//对数据进行签名 r，s
		r, s, err := ecdsa.Sign(rand.Reader, privKey, signData)
		if err != nil {
			fmt.Println("交易签名失败。err：", err)
		}
		//拼接r，s为字节流，赋值给原始的交易signature字段
		signature := append(r.Bytes(), s.Bytes()...)
		tx.TXInputs[i].Signature = signature
	}

}

// trim:裁切
// 》做相应裁剪，把每一个input的sig和pubkey设置为nil
// 》output不做改变
func (tx *Transaction) TrimmedCopy() Transaction {
	var inputs []TXInput
	var outputs []TXOutput

	for _, input := range tx.TXInputs {
		input1 := TXInput{
			TXID:      input.TXID,
			Index:     input.Index,
			Signature: nil,
			Pubkey:    nil,
		}
		inputs = append(inputs, input1)
	}

	outputs = tx.TXOutputs
	tx1 := Transaction{
		TXid:      tx.TXid,
		TXInputs:  inputs,
		TXOutputs: outputs,
	}
	return tx1
}

//校验签名
func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
	fmt.Println("对交易进行校验！")
	//1.拷贝修剪的副本
	txCopy:=tx.TrimmedCopy()
	//2.遍历原始交易
	for i,input:=range tx.TXInputs{
		//3.遍历原始交易的input所引用的前交易prevtx
		prevTX:=prevTXs[string(input.TXID)]
		output:=prevTX.TXOutputs[input.Index]

		//4.找到output的公钥哈希，赋值给txcopy对应的input
		txCopy.TXInputs[i].Pubkey=output.PubKeyHash

		txCopy.SetTXid()
		//清理动作
		txCopy.TXInputs[i].Pubkey=nil
		//5.还原签名的数据
		verifyData:=txCopy.TXid
		signature:=input.Signature

		//6.校验
		//公钥字节流
		pubKeyBytes:=input.Pubkey
		r:=big.Int{} //签名生成的r，s
		s:=big.Int{}
		rData:=signature[:len(signature)/2]
		sData:=signature[len(signature)/2:]
		r.SetBytes(rData)
		s.SetBytes(sData)

		//
		x:=big.Int{}
		y:=big.Int{}
		xData:=pubKeyBytes[:len(pubKeyBytes)/2]
		yData:=pubKeyBytes[len(pubKeyBytes)/2:]
		x.SetBytes(xData)
		y.SetBytes(yData)

		curve:=elliptic.P256()

		publicKey:=ecdsa.PublicKey{
			Curve: curve,
			X:     &x,
			Y:     &y,
		}

		if !ecdsa.Verify(&publicKey,verifyData,&r,&s) {
			//只要有一个错误就退出
			return false
		}

	}
	return true
}
